﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Input;

namespace Pavilion.GameTools
{
    public enum CharacterInput { Jump, Grab, Left, Right, Up, Down }
    public class CharacterInputTool : GameToolBase
    {
        public class PropertyController
        {
            private static Dictionary<string, Action<CharacterInputTool, object>> dataExecutionDictionary;
            private CharacterInputTool gameTool;

            public Keys Grab { set { gameTool.inputDictionary[CharacterInput.Grab].KeyInput = value; } }
            public Keys Jump { set { gameTool.inputDictionary[CharacterInput.Jump].KeyInput = value; } }
            public Keys Left { set { gameTool.inputDictionary[CharacterInput.Left].KeyInput = value; } }
            public Keys Right { set { gameTool.inputDictionary[CharacterInput.Right].KeyInput = value; } }
            public Keys Up { set { gameTool.inputDictionary[CharacterInput.Up].KeyInput = value; } }
            public Keys Down { set { gameTool.inputDictionary[CharacterInput.Down].KeyInput = value; } }

            public CharacterInput InputPress { set { } }
            public CharacterInput InputRelease { set { } }

            public PropertyController(CharacterInputTool gameTool)
            {
                this.gameTool = gameTool;
            }

            private void LoadDataExecution()
            {
                if (dataExecutionDictionary != null)
                    return;

                dataExecutionDictionary = new Dictionary<string, Action<CharacterInputTool, object>>();

                dataExecutionDictionary.Add("Grab", (characterInputTool, value) => characterInputTool.Grab = (Keys)value);
                dataExecutionDictionary.Add("Jump", (characterInputTool, value) => characterInputTool.Jump = (Keys)value);
                dataExecutionDictionary.Add("Left", (characterInputTool, value) => characterInputTool.Left = (Keys)value);
                dataExecutionDictionary.Add("Right", (characterInputTool, value) => characterInputTool.Right = (Keys)value);
                dataExecutionDictionary.Add("Up", (characterInputTool, value) => characterInputTool.Up = (Keys)value);
                dataExecutionDictionary.Add("Down", (characterInputTool, value) => characterInputTool.Down = (Keys)value);
                dataExecutionDictionary.Add("InputPress", (characterInputTool, value) => characterInputTool.InputPress = (CharacterInput)value);
                dataExecutionDictionary.Add("InputRelease", (characterInputTool, value) => characterInputTool.InputRelease = (CharacterInput)value);
            }

            public void UpdateProperty(string propertyName, object propertyValue)
            {
                dataExecutionDictionary[propertyName].Invoke(gameTool, propertyValue);
            }
        }

        private static Dictionary<string, Action<CharacterInputTool, object>> dataExecutionDictionary;

        public event Action<IEnumerable<CharacterInput>> PressedInputChanged, ReleasedInputChanged;
        public event Action<CharacterInput> InputPressed, InputReleased;

        public IEnumerable<CharacterInput> PressedInput { get { return pressedInput; } }
        public IEnumerable<CharacterInput> ReleasedInput { get { return releasedInput; } }

        public Keys Grab { set { inputDictionary[CharacterInput.Grab].KeyInput = value; } }
        public Keys Jump { set { inputDictionary[CharacterInput.Jump].KeyInput = value; } }
        public Keys Left { set { inputDictionary[CharacterInput.Left].KeyInput = value; } }
        public Keys Right { set { inputDictionary[CharacterInput.Right].KeyInput = value; } }
        public Keys Up { set { inputDictionary[CharacterInput.Up].KeyInput = value; } }
        public Keys Down { set { inputDictionary[CharacterInput.Down].KeyInput = value; } }

        public CharacterInput InputPress { set { inputDictionary[value].AddPressedInput(); } }
        public CharacterInput InputRelease { set { inputDictionary[value].AddReleasedInput(); } }

        private List<CharacterInput> pressedInput, releasedInput;
        private KeyboardTool keyboardTool;
        private Dictionary<CharacterInput, CharacterInputInfo> inputDictionary;

        private class CharacterInputInfo
        {
            public CharacterInput CharacterInput { get; private set; }
            public Keys KeyInput { get { return keyInput; } set { SetKeyInput(value); } }

            private CharacterInputTool gameTool;
            private Keys keyInput;

            public CharacterInputInfo(CharacterInput characterInput, CharacterInputTool gameTool)
            {
                this.CharacterInput = characterInput;
                this.gameTool = gameTool;
                this.keyInput = Keys.None;
            }

            public void AddPressedInput()
            {
                if (gameTool.pressedInput.Contains(CharacterInput))
                    return;

                gameTool.pressedInput.Add(CharacterInput);
                gameTool.releasedInput.Remove(CharacterInput);
                gameTool.OnInputPressed(CharacterInput);
                gameTool.OnPressedInputChanged();
            }

            public void AddReleasedInput()
            {
                if (gameTool.releasedInput.Contains(CharacterInput))
                    return;

                gameTool.releasedInput.Add(CharacterInput);
                gameTool.pressedInput.Remove(CharacterInput);
                gameTool.OnInputReleased(CharacterInput);
                gameTool.OnReleaseInputChanged();
            }

            private void SetKeyInput(Keys value)
            {
                gameTool.UnregisterCharacterInput(keyInput, CharacterInput);
                keyInput = value;
                gameTool.RegisterCharacterInput(keyInput, CharacterInput);
            }
        }

        public CharacterInputTool(GameItem parentGameItem) : base(parentGameItem)
        {
            pressedInput = new List<CharacterInput>();
            releasedInput = new List<CharacterInput>();

            inputDictionary = new Dictionary<CharacterInput, CharacterInputInfo>();

            foreach (CharacterInput characterInput in Enum.GetValues(typeof(CharacterInput)))
                inputDictionary.Add(characterInput, new CharacterInputInfo(characterInput, this));

            LoadDataExecution();
        }

        public override void Dispose()
        {
            while (inputDictionary.Count > 0)
            {
                CharacterInputInfo inputInfo = inputDictionary.First().Value;
                UnregisterCharacterInput(inputInfo.KeyInput, inputInfo.CharacterInput);
                inputDictionary.Remove(inputDictionary.First().Key);
            }

            base.Dispose();
        }

        private void LoadDataExecution()
        {
            if (dataExecutionDictionary != null)
                return;

            dataExecutionDictionary = new Dictionary<string, Action<CharacterInputTool, object>>();

            dataExecutionDictionary.Add("Grab", (characterInputTool, value) => characterInputTool.Grab = (Keys)value);
            dataExecutionDictionary.Add("Jump", (characterInputTool, value) => characterInputTool.Jump = (Keys)value);
            dataExecutionDictionary.Add("Left", (characterInputTool, value) => characterInputTool.Left = (Keys)value);
            dataExecutionDictionary.Add("Right", (characterInputTool, value) => characterInputTool.Right = (Keys)value);
            dataExecutionDictionary.Add("Up", (characterInputTool, value) => characterInputTool.Up = (Keys)value);
            dataExecutionDictionary.Add("Down", (characterInputTool, value) => characterInputTool.Down = (Keys)value);
            dataExecutionDictionary.Add("InputPress", (characterInputTool, value) => characterInputTool.InputPress = (CharacterInput)value);
            dataExecutionDictionary.Add("InputRelease", (characterInputTool, value) => characterInputTool.InputRelease = (CharacterInput)value);
        }

        public override void UpdateProperty(string propertyName, object propertyValue)
        {
            dataExecutionDictionary[propertyName].Invoke(this, propertyValue);
        }

        public override void Load()
        {
            keyboardTool = ParentGameItem.ObtainTool<KeyboardTool>();
        }

        private void RegisterCharacterInput(Keys key, CharacterInput characterInput)
        {
            releasedInput.Add(characterInput);

            keyboardTool.RegisterKeyPressed(inputDictionary[characterInput].AddPressedInput, key);
            keyboardTool.RegisterKeyReleased(inputDictionary[characterInput].AddReleasedInput, key);
        }

        private void UnregisterCharacterInput(Keys key, CharacterInput characterInput)
        {
            pressedInput.Remove(characterInput);
            releasedInput.Remove(characterInput);

            keyboardTool.UnregisterKeyPressed(inputDictionary[characterInput].AddPressedInput, key);
            keyboardTool.UnregisterKeyReleased(inputDictionary[characterInput].AddReleasedInput, key);
        }

        private void OnReleaseInputChanged()
        {
            if (ReleasedInputChanged != null)
                ReleasedInputChanged(ReleasedInput);
        }

        private void OnPressedInputChanged()
        {
            if (PressedInputChanged != null)
                PressedInputChanged(PressedInput);
        }

        private void OnInputPressed(CharacterInput input)
        {
            if (InputPressed != null)
                InputPressed(input);
        }

        private void OnInputReleased(CharacterInput input)
        {
            if (InputReleased != null)
                InputReleased(input);
        }
    }
}
